package com.xcity.robot.net.websocket;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderAdapter;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;

import com.xcity.robot.net.Session;
import com.xcity.robot.net.util.SessionUtils;

import naga.x.net.impl.websocket.WebSocketConst;
import naga.x.util.IOUtils;
import naga.x.util.StringUtils;

public class WebsocketDecoder extends ProtocolDecoderAdapter {

	protected static final String HANDSHAKE_HEADERS = WebsocketDecoder.class.getName() + ".handshake";
	protected static final String BUFFER = WebsocketDecoder.class.getName() + ".buffer";
	
	public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
		IoBuffer buf = (IoBuffer) session.getAttribute(BUFFER);
		boolean useSessionBuffer = false;
		if (buf != null) {
			useSessionBuffer = true;
			buf.put(in);
			buf.flip();
			in = buf;
		}
		
		while (in.hasRemaining() && doDecode(session, in, out)) {
			// nothing
		}
		
		if (in.hasRemaining()) {
			if (useSessionBuffer) {
				in.compact();
			} else {
				buf = IoBuffer.allocate(in.remaining()).setAutoExpand(true).order(in.order()).put(in);
				session.setAttribute(BUFFER, buf);
			}
		} else if (useSessionBuffer) {
			session.removeAttribute(BUFFER);
		}
	}
	
	protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) {
		if (session.getAttribute(HANDSHAKE_HEADERS) != null) {
			return doDecode0(session, in, out);
		}
		return doHandshake(session, in, out);
	}

	protected boolean doDecode0(IoSession session, IoBuffer in, ProtocolDecoderOutput out) {
		in.mark();
		byte op = (byte) (in.get() & WebSocketConst.OP_MASK);
		if (!in.hasRemaining()) {
			in.reset();
			return false;
		}
		if (op == WebSocketConst.OP_BINARY) {
			int len = in.get() & WebSocketConst.PLAYLOAD_LEN_MASK;
			if (len < WebSocketConst.PLAYLOAD_LEN_2_BYTES) {
				if (in.remaining() < len) {
					in.reset();
					return false;
				}
			} else if (len == WebSocketConst.PLAYLOAD_LEN_2_BYTES) {
				if (in.remaining() < 2) {
					in.reset();
					return false;
				}
				len = in.getShort();
				if (in.remaining() < len) {
					in.reset();
					return false;
				}
			} else if (len == WebSocketConst.PLAYLOAD_LEN_8_BYTES) {
				if (in.remaining() < 8) {
					in.reset();
					return false;
				}
				len = (int) in.getLong();
				if (in.remaining() < len) {
					in.reset();
					return false;
				}
			}
			byte[] buf = new byte[len];
			in.get(buf);
			out.write(IoBuffer.wrap(buf));
		}
		return true;
	}
	
	protected boolean doHandshake(IoSession session, IoBuffer in, ProtocolDecoderOutput out) {
		byte[] buf = new byte[in.remaining()];
		in.get(buf);
		String headers = StringUtils.bytesToString(buf, IOUtils.UTF8);
		session.setAttribute(HANDSHAKE_HEADERS, headers);
//		HandshakeListener listener = (HandshakeListener) session.getAttribute(SessionUtils.HANDSHAKE_LISTENER);
		HandshakeListener listener = HandshakeListener.WEBSOCKET_HANDSHAKE;
		if (listener != null) {
			Session sess = (Session) session.getAttribute(SessionUtils.SESSION);
			listener.handshakeOk(sess);
		}
		return true;
	}

}
